/*
 * MIXLIB.C Version 1.00 (C)1996 HUDSON SOFT
 * MIX 再生ライブラリ関数(MIXLIB.C)
 */

/* K-RAM の先頭アドレスの定義
 * KG_kram_A ADPCM #0と動画
 * KG_kram_B ADPCM #1
 */
#define	KG_kram_A		0x00000000
#define	KG_kram_B		0x00020000

#include    <system.h>
#include    <scsi.h>
#include    <nt.h>
#include    <king_kr.h>
#include    <king_ks.h>
#include    <soundbox.h>
#include    "cdrscsi.h"
#include    "mixlib.h"

/* 動画実行ステータス */
#define	MIX_PLAY_STATUS_WAIT	0
#define	MIX_PLAY_STATUS_EXEC	1

/* 動画ファイルのヘッダ情報サイズ */
#define	MAX_MIX_HEAD		128

/* K-RAM の大きさ（16bit を１ワードとした時のワード数） */
#define	KRAM_SIZE		(128*1024)

/* ブロック数（０〜１５）によって _KR_TSR 、_KR_LEN に書き込む値の表
 * ブロック数は動画ファイルのヘッダから取得します。
 */
static unsigned int	mix_dct[16][2] = {
    (8*1),  15,	    /* 0 */
    (8*15), 1,	    /* 1 */
    (8*14), 2,	    /* 2 */
    (8*13), 3,	    /* 3 */
    (8*12), 4,	    /* 4 */
    (8*11), 5,	    /* 5 */
    (8*10), 6,	    /* 6 */
    (8*9),  7,	    /* 7 */
    (8*8),  8,	    /* 8 */
    (8*7),  9,	    /* 9 */
    (8*6),  10,	    /* 10 */
    (8*5),  11,	    /* 11 */
    (8*4),  12,	    /* 12 */
    (8*3),  13,	    /* 13 */
    (8*2),  14,	    /* 14 */
    (8*1),  15,	    /* 15 */
};

/* 動画データ読み込みの為のワーク変数 */
int			mix_tsr_ofs=0;
static unsigned int	mix_dct_tsr;
static unsigned int	mix_dct_len;
unsigned int		mix_play_errt=0;

/* ADPCM 再生の為のパラメータ構造体 */
static struct KS adpcm = {
    0x00,				/* unsigned short	ksmode;   */
    0x07,				/* unsigned short	kscr0;	  */
    0x07,				/* unsigned short	kscr1;	  */
    (KG_kram_A|0)>>8,			/* unsigned short	ksstart0; */
    (KG_kram_A|(KRAM_SIZE)-1),		/* unsigned long	ksend0;   */
    (KG_kram_A|(KRAM_SIZE/2))>>6,	/* unsigned short	kshalf0;  */
    (KG_kram_B|0)>>8,		 	/* unsigned short	ksstart1; */
    (KG_kram_B|(KRAM_SIZE)-1),	 	/* unsigned long	ksend1;   */
    (KG_kram_B|(KRAM_SIZE/2))>>6,	/* unsigned short	kshalf1;  */
};

/* ADPCM の再生状態を示すワーク変数 */
static unsigned int	adpcm_flag[2]={
    0,
    0,
};

/* 動画再生の為のワーク変数 */
static int		mix_play_status=MIX_PLAY_STATUS_WAIT;
static unsigned int	mix_play_vsync=0;
static int		mix_play_scsi=0;
static MIX_HEAD		*mix_play_mixp=NULL;

/* read28() の為の構造体 */
READ28	read28_stat;

/* 動画、音声の読み込み位置、再生位置を制御するワーク変数 */
static unsigned int	*read_mixp;
static unsigned int	*disp_mixp;
static unsigned int	read_adp[2];
static unsigned int	exec_adp[2];
static unsigned int	read_dct;
static unsigned int	exec_dct;
static unsigned int	disp_dct;
static unsigned int	back_dct;
static int		read_exec;

/* 指定の ADPCM の再生を指示します。
 * 実際の再生開始は mix_play_adpcm_check() 内で行なわれます。
 *
 * 入力：
 * int		    ch;    再生する ADPCM のチャンネル番号
 *
 * 出力：
 * なし
 */
static void	mix_play_adpcm_exec(ch)
int	ch;
{
    adpcm_flag[ch]=1;
}

/* 指定の ADPCM の停止を指示します。
 * 実際の再生停止は mix_play_adpcm_check() 内で行なわれます。
 *
 * 入力：
 * int		    ch;    停止する ADPCM のチャンネル番号
 *
 * 出力：
 * なし
 */
static void	mix_play_adpcm_stop(ch)
int	ch;
{
    adpcm_flag[ch]=0;
}

/* ADPCM の再生状態を示すワーク変数を元に ADPCM の再生開始と再生停止を行ないま
 * す。
 *
 * 入力：
 * なし
 *
 * 出力：
 * なし
 */
static void	mix_play_adpcm_check(void)
{
    unsigned int	ch;
    unsigned int	ef;
    unsigned int	mm;
    unsigned int	mode_back;
    unsigned int	mode_next;

    ef = 0 ;
    mode_back = adpcm.ksmode ;
    mode_next = adpcm.ksmode ;

    for ( ch=0 ; ch < 2 ; ch++ ) {
	mm = (0x01 << ch) ;
	if ( adpcm_flag[ch] != 0 ) {
	    mode_next |= mm ;
	} else {
	    mode_next &= ~mm ;
	}
    }
    if ( mode_back == mode_next ) {
	return;
    }

    adpcm.ksmode = mode_next;
    set_ks_pram(&adpcm);
}

/* read28() によるバックグラウンド CD-ROM 読み込みを開始します。
 *
 * 入力：
 * int		    id;    CD-ROM（ターゲット）の SCSI ID
 * unsigned int	    sect;  読み込み開始ブロック位置
 * unsigned int	    size;  読み込みブロック数
 *
 * 出力：
 * int		    0      読み込みが開始された
 *		    -1     読み込みに失敗した
 */
static int	mix_read_start(id,sect,size)
int		id;
unsigned int	sect;
unsigned int	size;
{
    read28_stat.id_bit = (0x01<<id);
    read28_stat.block = sect;
    read28_stat.blklen = size;
    return( open_read28( &read28_stat ) );
}

/* K-RAM に読み込まれた動画データ、音声データの転送を制御し、再生開始、再生停止
 * を行ないます。
 *
 * 以下の状態にある場合は何もしないで戻ります。
 *  ・初回の呼び出しで read28() が CD-ROM 読み込み中の場合
 *  ・次の表示フレームタイミングに達していない場合
 *  ・表示すべきデータがまだ読み込まれていない場合
 *
 * 以下の状態にある場合は再生を終了します。
 *  ・次に再生すべき動画データが無く、データの終りに達した場合
 *
 * 以下の状態にある場合は再生を中断して異常終了します。
 *  ・データの表示が間に合わず表示フレームタイミングを通り越してしまった場合
 *
 * 入力：
 * なし
 *
 * 出力：
 * なし
 */
static void	mix_disp()
{
    unsigned int	disp;
    unsigned int	rsiz;
    unsigned int	rpos;
    unsigned int	vsync_next;

    if ( mix_play_vsync == 0 ) {	/* 初回の呼び出しか？ */
	switch( read28_stat.stat ) {
	case READ28_READ:
	    return;			/* まだ読み込み中であれば何もしない */
	default:
	    mix_play_vsync = vsync_count ;
	    break;			/* 読込済みであれば現在の表示フレー */
	}				/* ムタイミングを記録して処理を継続 */
    }

    if ( mix_play_vsync > vsync_count ) {
	return;				/* 次の表示タイミングに達していない */
    }

    switch( (mix_play_mixp->fps) ) {	/* １秒間のフレーム数を元に次の表示 */
    case  1:	vsync_next=60;	break;	/* フレームタイミングを求める */
    case  2:	vsync_next=30;	break;
    case  3:	vsync_next=20;	break;
    case  4:	vsync_next=15;	break;
    case  5:	vsync_next=12;	break;
    case  6:	vsync_next=10;	break;
    case 10:	vsync_next=6;	break;
    case 12:	vsync_next=5;	break;
    case 15:	vsync_next=4;	break;
    case 20:	vsync_next=3;	break;
    case 30:	vsync_next=2;	break;
    case 60:	vsync_next=1;	break;
    default:	vsync_next=2;	break;
    }

    disp=*disp_mixp;			/* 制御データとデータ長を取得 */
    rsiz = (disp & MIX_SZM);
    rsiz /= 2 ;				/* データ長 */

    if ((disp >> MIX_CHS) == MIX_EOF) {	/* 制御データが終了コードなら */
	mix_play_status = MIX_PLAY_STATUS_WAIT ;
	mix_play_adpcm_stop(0);
	mix_play_adpcm_stop(1);
	mix_play_adpcm_check();		/* すべての再生を停止して終了 */
	return;
    }
    if ( read_mixp == disp_mixp ) {
	return;				/* 表示すべきデータがまだ読み込まれ */
    }					/* ていない */

    disp_mixp++;			/* 表示データ位置を更新 */

    switch( (disp >> MIX_CHS) ) {	/* 制御データにより処理を選択 */
    case MIX_NUL:
	break;				/* 何もしない */
    case MIX_NOP:
	mix_play_vsync += vsync_next;	/* 次のタイミングまで待つ */
	if ( mix_play_vsync < vsync_count ) {
	    mix_play_errt = 1 ;		/* 表示処理が間に合わないエラー */
	}
	break;
    case MIX_AdL:
	mix_play_adpcm_exec(0);		/* ADPCM 左チャンネル再生 */
	exec_adp[0] += rsiz ;
	break;
    case MIX_AnL:
	mix_play_adpcm_stop(0);		/* ADPCM 左チャンネル停止 */
	break;
    case MIX_AdR:
	mix_play_adpcm_exec(1);		/* ADPCM 右チャンネル再生 */
	exec_adp[1] += rsiz ;
	break;
    case MIX_AnR:
	mix_play_adpcm_stop(1);		/* ADPCM 右チャンネル停止 */
	break;
    case MIX_DCT:
	back_dct = disp_dct;		/* 動画の再生 */
	rpos = exec_dct ;
	disp_dct = rpos;
	exec_dct = rpos+rsiz;
	if ( rpos == 0 ) {
	    __OUTPORTH( IO_KING_AR,_KR_TSR);
	    __OUTPORTH( IO_KING_DR,mix_dct_tsr+mix_tsr_ofs);
	    __OUTPORTH( IO_KING_AR,_KR_LEN);
	    __OUTPORTH( IO_KING_DR,mix_dct_len);
	    set_kr_trans(KR_TRANS_ON);
	}
	rpos %= KRAM_SIZE;
	mix_play_vsync += vsync_next;
	if ( mix_play_vsync < vsync_count ) {
	    mix_play_errt = 1 ;		/* 表示処理が間に合わないエラー */
	}
	__OUTPORTH( IO_KING_AR,_KR_TSA );
	__OUTPORTW( IO_KING_DR,(KG_kram_A|rpos) );
	break;
    }
    mix_play_adpcm_check();		/* ADPCM の再生／停止の制御 */
}

/* SCSI の DMA 転送を起動します。
 *
 * 初回に mix_read() から呼ばれて DMA 転送が起動された後は、DMA 転送終了割り込
 * みの処理ルーチンから、この関数が呼び出されて DMA 転送が継続されます。
 *
 * K-RAM が未再生データで埋まり DMA 転送が終了してしまった場合も mix_read() か
 * らこの関数が呼び出しされ DMA 転送が再開されます。
 *
 * 入力：
 * なし
 *
 * 出力：
 * なし
 */
void	mix_read_int()
{
    unsigned int	read;
    unsigned int	rsiz;
    unsigned int	rpos;
    unsigned int	tmp ;

    if ( read28_stat.stat == READ28_IDLE ) {
					/* 次の転送開始待ちの状態の時のみ */
	if ( read_exec != 0 ) {
	    read_exec = 0 ;		/* 前回の読み込み実行の直後ならば */
	    read_mixp++;		/* 読み込み位置を前回の分だけ進める */
	}

	for(;;){			/* 何かデータを読み込むまで繰り返し */
	    __OUTPORTH(IO_KING_AR, _KRAM_ARR);
	    __OUTPORTW(IO_KING_DR, 0);
	    __OUTPORTH(IO_KING_AR, _KRAM_DATA);
	    tmp=__INPORTH(IO_KING_DR);	/* 割り込み処理中の場合があるので */
	    tmp=__INPORTH(IO_KING_DR);	/* K-RAM のダミーアクセスを行ない */
	    tmp=__INPORTH(IO_KING_DR);	/* 引き続く K-RAM アクセスを保証する */
	    tmp=__INPORTH(IO_KING_DR);

	    read=*read_mixp;		/* 現在の読込位置の制御データと長さ */
	    rsiz = (read & MIX_SZM);
	    rsiz /= 2 ;			/* 読み込む長さ */

	    switch( (read >> MIX_CHS) ) {
	    case MIX_EOF:
		return;			/* 制御データが終了コード */
	    case MIX_NOP:
		read_mixp++;		/* 読むデータが無いので次に進める */
		break;
	    case MIX_AdL:
		rpos = read_adp[0] ;	/* ADPCM の左チャンネルを読む */
		if ( (rpos+rsiz) < (exec_adp[0]+(KRAM_SIZE-1024)) ){
		    read_adp[0] = rpos + rsiz ;
		    rpos %= KRAM_SIZE ;
		    __OUTPORTH(IO_KING_AR,_KRAM_PAGE);
		    __OUTPORTH(IO_KING_DR,0x0101);
		    read28((KG_kram_A|rpos),rsiz*2);
		    read_exec=1;
		}
		return;			/* 読み終ったら関数からリターンする */
	    case MIX_AdR:
		rpos = read_adp[1] ;	/* ADPCM の右チャンネルを読む */
		if ( (rpos+rsiz) < (exec_adp[1]+(KRAM_SIZE-1024)) ){
		    read_adp[1] = rpos + rsiz ;
		    rpos %= KRAM_SIZE ;
		    __OUTPORTH(IO_KING_AR,_KRAM_PAGE);
		    __OUTPORTH(IO_KING_DR,0x0101);
		    read28((KG_kram_B|rpos),rsiz*2);
		    read_exec=1;
		}
		return;			/* 読み終ったら関数からリターンする */
	    case MIX_AnL:
		read_mixp++;		/* 読むデータが無いので次に進める */
		break;
	    case MIX_AnR:
		read_mixp++;		/* 読むデータが無いので次に進める */
		break;
	    case MIX_NUL:
	    case MIX_DCT:
		rpos = read_dct ;	/* 動画データを読み込む */
		if ( (rpos+rsiz) < (back_dct+KRAM_SIZE) ) {
		    read_dct = rpos+rsiz;
		    rpos %= KRAM_SIZE;
		    __OUTPORTH(IO_KING_AR,_KRAM_PAGE);
		    __OUTPORTH(IO_KING_DR,0x0100);
		    read28( (KG_kram_A|rpos),rsiz*2);
		    read_exec=1;
		}
		return;			/* 読み終ったら関数からリターンする */
	    }
	}
    }
}

/* CD-ROM からの動画音声データ読み込みの DMA 転送を制御します。
 *
 * 動画再生状態にない場合は何もしません。
 *
 * 転送待ちの状態の場合は DMA 転送を起動します。
 *
 * 読み込み中の場合は何もしません。
 *
 * 最後まで転送し終った場合、途中で中断された場合は動画再生を終了させます。
 *
 * 入力：
 * なし
 *
 * 出力：
 * なし
 */
static void	mix_read()
{
    if ( mix_play_scsi == 0 ) {		/* 動画再生状態ではないなら */
	return;				/* 何もしない */
    }
    switch( read28_stat.stat ) {
    case READ28_IDLE:			/* 次の転送待ちなら */
	mix_read_int();			/* DMA 転送を起動する */
	break;
    case READ28_READ:			/* 読み込み中なら */
	break;				/* 何もしない */
    case READ28_END:			/* 読み込み終了なら */
	if ( read_exec != 0 ) {
	    read_exec = 0 ;
	    read_mixp++;
	}
	close_read28() ;		/* 読み込み終了手続きを行ない */
	mix_play_scsi=0;		/* 動画再生を終了する */
	break;
    case READ28_BREAK:			/* 読み込みが中断されたら */
	close_read28() ;		/* 読み込み終了手続きを行ない */
    default:
	mix_stop();			/* 動画再生を中止する */
	break;
    }
}

/* 動画音声データの再生準備を行ない、再生を開始します。
 *
 * 入力：
 * int		    id;    CD-ROM（ターゲット）の SCSI ID
 * unsigned int	    sect;  再生データ読み込み開始ブロック位置
 * unsigned int	    fram;  未使用
 *
 * 出力：
 * int		    0      再生が開始された
 *		    -1     再生されなかった
 */
int	mix_start(id,sect,fram)
int		id;
unsigned int	sect;
unsigned int	fram;
{
    static unsigned char	read_buff[MAX_MIX_HEAD][2048];
    MIX_HEAD			*mix;
    unsigned int		v;

    if ( mix_play_status == MIX_PLAY_STATUS_EXEC ) {
	return(-1);			/* 既に再生中だった */
    }
    cdrom_read(id,sect,read_buff[0],1);	/* 再生制御データを読み込む */
    mix = (MIX_HEAD *)read_buff[0];
    mix_play_mixp = mix ;		/* 再生開始位置をセット */
    if ( strcmp(mix->mixs,MIX_MAGIC) != 0 && strcmp(mix->mixs,MIX_MAGIC) != 0 ) {
	return(-1);			/* 再生制御データのマークが無かった */
    }
    if ( mix->hrec > MAX_MIX_HEAD ) {	/* 再生制御データの最大ブロック数を */
	return(-1);			/* 超えているので再生できない */
    }
    if ( mix->hrec > 1 ) {		/* 制御データが２ブロック以上なら */
	cdrom_read(id,sect+1,read_buff[1],mix->hrec-1);	/* 続きを読む */
    }
    if ( mix_read_start(id,sect+mix->hrec,mix->drec) != 0 ) {
	return(-1);			/* 動画データの読み込み開始に失敗 */
    }

    mix_play_scsi=1;			/* 内部変数ワークを初期化 */
    mix_dct_tsr = mix_dct[mix->blck][0];
    mix_dct_len = mix_dct[mix->blck][1];
    mix_play_status = MIX_PLAY_STATUS_EXEC ;
    mix_play_vsync = 0 ;		/* 再生開始フレーム番号を初期化 */

    read_adp[0] = 0 ;
    read_adp[1] = 0 ;
    exec_adp[0] = 0 ;
    exec_adp[1] = 0 ;
    read_dct = 0;
    exec_dct = 0;
    disp_dct = 0;
    back_dct = 0;
    read_exec = 0 ;
    disp_mixp = read_mixp = &(mix->data[0]) ;

    reset_adp(ADP_RESET_ALL);		/* ADPCM を初期化 */
    switch( ((mix->adpt) & 0x07) ) {	/* 音声データの再生レートによって */
    case 1:				/* ADPCM の再生周波数をセットする */
	adpcm.ksmode=0x0c;
	set_adp_rate(ADPCM_RATE4);
	break;
    case 2:
	adpcm.ksmode=0x08;
	set_adp_rate(ADPCM_RATE8);
	break;
    case 3:
	adpcm.ksmode=0x04;
	set_adp_rate(ADPCM_RATE16);
	break;
    default:
	adpcm.ksmode=0x00;
	set_adp_rate(ADPCM_RATE32);
	break;
    }

    if( ((mix->adpt) & 0x40) == 0 ) {	/* １画面のラスタ数の選択 */
	set_nt_mode(mode_5M|inter_sync|line263);
    } else {
	set_nt_mode(mode_5M|inter_sync|line262);
    }

    set_ks_pram(&adpcm);		/* ADPCM のパラメータのセット */

    set_kr_trans(KR_TRANS_OFF);		/* HuC6271 への K-RAM DMA 転送を停止 */
    v=vsync_count;			/* 垂直同期割り込みを２回待って */
    while( v== vsync_count )		/* HuC6271 の表示オフを待つ */
	;
    v=vsync_count;
    while( v== vsync_count )
	;
    return(0);
}

/* 動画音声の再生を停止します。
 *
 * 再生中でないなら何もしません。
 *
 * 入力：
 * なし
 *
 * 出力：
 * なし
 */
void	mix_stop()
{
    if ( mix_play_status != MIX_PLAY_STATUS_WAIT ) {	/* 再生しているなら */
	if ( mix_play_scsi != 0 ) {	/* CD-ROM 読み込み中なら */
	    close_read28() ;		/* CD-ROM の読み込みを停止する */
	    mix_play_scsi=0;
	}
	mix_play_status = MIX_PLAY_STATUS_WAIT ;	/* 停止を指示する */
	mix_play_adpcm_stop(0);		/* ADPCM の左チャンネル停止を指示 */
	mix_play_adpcm_stop(1);		/* ADPCM の右チャンネル停止を指示 */
	mix_play_adpcm_check();		/* ADPCM の再生を停止させる */
    }
}

/* 動画再生中かどうかを調べ、再生中なら表示と読み込みを行ない、再生中かどうかを
 * 返します。
 *
 * 入力：
 * なし
 *
 * 出力：
 * int		    0      再生中だった
 *		    -1     再生が終了した
 */
int	mix_check()
{
    if ( mix_play_status == MIX_PLAY_STATUS_WAIT ) {
	return(-1);			/* 再生が終了している */
    }
    mix_disp();				/* 動画データの表示 */
    mix_read();				/* 動画データの読み込み */
    return(0);
}
